/** Author Ayla Brayer **/
%{ open Ast %}

%token LPAREN RPAREN RSBRACE LSBRACE LBRACE RBRACE SEMI PLUS TIMES ASSIGN EQ
%token NEQ LEQ GEQ LT GT MINUS DIVIDE AND OR DEFAULT REPEAT RTFILE 
%token RTSTDOUT PRINT INT FLOAT STRING BOOL TRUE FALSE EOF
%token <int> LITERAL
%token <bool> TRUE FALSE
%token <string> ID
%token <string> STR
%token <float> FLOATPT

%token <Ast.exprn> EXP 

/*precedence rules*/
%nonassoc NOQUESTION
%right  ASSIGN
%left  AND OR
%left  EQ NEQ
%left  LT GT LEQ GEQ
%left  PLUS MINUS
%left  TIMES DIVIDE 
%left BOOL INT STRING FLOAT 

%start program
%type <Ast.program> program

%%

/* 
a GAQ program consists of variable declaration followed by 
questions followed by statements. we enforce this order here 
*/
program:
/*nothing*/	              {([],[],[])}
| program var_decleration { let (v, q, e) = 
      $1 in match q with 
		    [] -> (match e with
				   [] -> ($2 :: v, q, e)
				   |h::t -> raise (Failure("declaration out of order")))
            |head::tail -> raise (Failure("out of order"))}
| program question		  { let (v, q, e) = 
      $1 in match e with 
	        []->  (v, $2 :: q , e) 
			|h::t -> raise (Failure("question out of order"))}
| program statement       { let (v, q, e) = $1 in (v, q, $2 :: e) } 



/* 
parse variable declaration. We already enforce correct types at 
this point
*/
var_decleration:
INT ID ASSIGN LITERAL SEMI {IntDeclaration ("int", $2, $4)}
| INT ID ASSIGN LPAREN MINUS LITERAL RPAREN SEMI {IntDeclaration ("int", $2, -1*$6)}
| FLOAT ID  ASSIGN LPAREN MINUS FLOATPT RPAREN SEMI { FloatDeclaration ("float", $2, -1.0*.$6) }
| FLOAT ID  ASSIGN FLOATPT SEMI { FloatDeclaration ("float", $2, $4) }
| STRING ID ASSIGN STR SEMI { StringDeclaration ("string", $2, $4) }
| BOOL ID ASSIGN TRUE SEMI { BoolDeclaration ("bool", $2, true) } 
| BOOL ID ASSIGN FALSE SEMI { BoolDeclaration ("bool", $2, false) } 

/*expressions*/
exprn:
  LITERAL		  { Literal ($1) }
| ID			  { Id ($1) }
| STR			  { Str ($1) }
| FLOATPT		  { Float ($1) }
| TRUE			  { Bool(true) }
| FALSE			  { Bool(false) }
/* binary operations */
| exprn EQ exprn  { Binop($1, Equal, $3) }
| exprn NEQ exprn { Binop($1, Neq, $3) }
| exprn LT exprn { Binop($1, Less, $3) }
| exprn LEQ exprn { Binop($1, Leq, $3) }
| exprn GT exprn { Binop($1, Greater, $3) }
| exprn GEQ exprn { Binop($1, Geq, $3) }
| exprn PLUS exprn { Binop($1, Add, $3) }
| exprn MINUS exprn { Binop($1, Sub, $3) }
| exprn TIMES exprn { Binop($1, Mult, $3) }
| exprn DIVIDE exprn { Binop($1, Div, $3) }
| exprn AND exprn { Binop($1, And, $3) }
| exprn OR exprn { Binop($1, Or, $3) } 
/* assigmnet */
| ID ASSIGN exprn { Assign($1, $3) }
/* 
negative nums: to save a headache later in the compiler, we filter out
the forbidden negation types already here. Not very elegant, but 
that's life
*/
| LPAREN MINUS LITERAL RPAREN {IntNegation($3)}
| LPAREN MINUS FLOATPT RPAREN {FloatNegation($3)}
| LPAREN MINUS ID RPAREN      {IdNegation($3)}
| LPAREN PLUS FLOATPT RPAREN  {Float ($3)}
| LPAREN PLUS LITERAL RPAREN  {Literal ($3)}
| LPAREN PLUS ID RPAREN       {Id ($3)}
| LPAREN exprn RPAREN         {Exprn($2)}

/* variable types */
vartype:
  INT     {"int"}
| STRING  {"string"}
| BOOL	  {"bool"}
| FLOAT   {"float"}

/*statement list*/
statement_list:
						   { [] }
| statement_list statement { $2 :: $1 } 

/*
a statement can be a print statement or an
expression followed by a semicolon
*/
statement:
   exprn SEMI                      { Expr($1) }
|  PRINT LPAREN STR RPAREN SEMI    { Print($3) }

/*question*/
question:
  STR vartype answer_list   {$1, $2, List.rev $3}
 
/*answer*/
answer:
  RSBRACE exprn LSBRACE LBRACE answer_block RBRACE {$2, $5}
| RSBRACE DEFAULT LSBRACE LBRACE answer_block RBRACE {Bool(true), $5} 

/*answer list*/
answer_list:
					     { [] }
 |answer_list answer { $2 :: $1 } 

answer_block:
/*no nested question*/
   statement_list %prec NOQUESTION  {Block(List.rev $1)}  
/*repeat statementmust applear at the end od the answer block*/
|  statement_list REPEAT SEMI       {Repeat(List.rev $1)} 
/* nested question */
|  statement_list question          {AnswBlock(List.rev $1, $2)}
